package me.youline.dataServer.service.support.sms;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.Date;
import java.util.Random;

import me.youline.dataServer.exception.SmsVerificationCodeSendException;
import me.youline.dataServer.exception.SmsVerificationCodeSendLimitedException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.http.ParseException;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;

@Service
public class CloopenSmsVerificationService {
	
	@Autowired
	private CloseableHttpClient client;
	
	@Value("#{frontProperties['cloopen.account.sid']}")
	private String accountSid;

	@Value("#{frontProperties['cloopen.account.token']}")
	private String accountToken;

	@Value("#{frontProperties['cloopen.appId']}")
	private String appId;
	
	@Value("#{frontProperties['cloopen.templateId']}")
	private String templateId;
	
	@Value("#{frontProperties['cloopen.baseUrl']}")
	private String baseUrl;

	private String sendSmsUrl = "/SMS/TemplateSMS";
	
	@Value("#{frontProperties['cloopen.code.validMinute']}")
	private int codeValidMinute;
	
	@Autowired
	private SmsCodeCacheService cacheService;
	
	public CloopenSmsVerificationService(){}

	protected CloopenSmsVerificationService(CloseableHttpClient client,
			String accountSid, String accountToken,String appId,
			String templateId, String baseUrl, String sendSmsUrl,int validCodeMinute,SmsCodeCacheService codeCacheService) {
		this.client = client;
		this.accountSid = accountSid;
		this.appId = appId;
		this.accountToken = accountToken;
		this.templateId = templateId;
		this.baseUrl = baseUrl;
		this.sendSmsUrl = sendSmsUrl;
		this.cacheService = codeCacheService;
		this.codeValidMinute = validCodeMinute;
	}

	/**
	 * 判断是否为有效的验证码
	 * @param code 验证码
	 * @param phone 手机号
	 * @return
	 */
	public boolean isValidCode(String code, String phone) {
		return cacheService.isValidCode(phone, code);
	}

	/**
	 * 向指定手机发送验证码，具体http请求参数参见{@link <a href="http://www.yuntongxun.com/activity/smsDevelop#tiyan">容联云通讯短信接口文档</a>}
	 * @param phone
	 * @throws SmsVerificationCodeSendException
	 */
	public void sendSms(String phone) throws SmsVerificationCodeSendException {
		String code = getVerificationCode();
		String timestamp = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss");
		RequestBuilder builder = RequestBuilder.post();
		setReqUrl(timestamp, builder);
		setReqHeader(timestamp, builder);
		setReqData(phone, code, builder);
		String res = handleRequestAndResponse(builder.build());
		transformResultException(res);
		cacheService.addCode(phone, code);
	}
	
	/**
	 * 执行请求并执行响应结果的处理
	 * @param req
	 * @return
	 * @throws SmsVerificationCodeSendException
	 */
	private String handleRequestAndResponse(HttpUriRequest req) throws SmsVerificationCodeSendException{
		try {
			 return EntityUtils.toString(client.execute(req).getEntity());
		} catch (ParseException | IOException e) {
			throw new SmsVerificationCodeSendException("与短信服务第三方平台通信发生错误");
		}
		
	}
	
	/**
	 * 设置请求url
	 * @param timestamp
	 * @param builder
	 */
	private void setReqUrl(String timestamp, RequestBuilder builder){
		builder.setUri(baseUrl + accountSid + sendSmsUrl + "?sig="+ getAccountSig(timestamp));
	}
	
	/**
	 * 设置请求体信息
	 * @param phone
	 * @param code
	 * @param builder
	 */
	private void setReqData(String phone,String code, RequestBuilder builder){
		JSONObject a = new JSONObject();
		
		a.put("to", phone);
		a.put("appId", appId);
		a.put("templateId", templateId);
		a.put("datas",
				new String[] { code,
						String.valueOf(codeValidMinute) });
		
		builder.setEntity(
				new StringEntity(a.toJSONString(),
						ContentType.APPLICATION_JSON));
	}
	
	/**
	 * 设置请求头
	 * @param timestamp
	 * @param builder
	 */
	private void setReqHeader(String timestamp,RequestBuilder builder){
		
		builder.addHeader("Accept", "application/json")
		.addHeader("Content-Type", "application/json;charset=utf-8")
		.addHeader("Authorization", getAuthCode(timestamp));
	}

	/**
	 * 创建与第三方服务请求的认证身份码
	 * @param timestamp
	 * @return
	 */
	protected String getAuthCode(String timestamp) {
		try {
			String src = accountSid + ":" + timestamp;
			return Base64.encodeBase64String(src.getBytes("utf-8"));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 创建账户的签名参数
	 * @param timestamp
	 * @return
	 */
	private String getAccountSig(String timestamp){
		try {
			String sig = accountSid + accountToken + timestamp;
			MessageDigest md = MessageDigest.getInstance("MD5");
			byte[] b = md.digest(sig.getBytes("utf-8"));
			return Hex.encodeHexString(b);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 创建随机验证码
	 * @return
	 */
	protected String getVerificationCode() {
		Random r = new Random(new Date().getTime());
		int num = r.nextInt(9999 - 1000) + 1000;//获取四位数字的验证码
		return String.valueOf(num);
	}

	/**
	 * 根据响应结果确定异常 ，
	 * 数据异常参见 {@link <a href="http://www.yuntongxun.com/activity/errorCode#tiyan">容联云通讯错误码文档</a>}
	 * @param responseEntity
	 * @throws SmsVerificationCodeSendException
	 */
	protected void transformResultException(String responseEntity) throws SmsVerificationCodeSendException{
		JSONObject res = JSONObject.parseObject(responseEntity);
		if(res == null || StringUtils.isBlank(res.get("statusCode").toString())){
			throw new SmsVerificationCodeSendException("短信服务第三方通信响应数据为空");
		}
		String statusCode = (String) res.get("statusCode");
		if("000000".equals(res.get("statusCode"))){//send sms successfully
			return;
		}
		if("160022".equals(statusCode)){
			throw new SmsVerificationCodeSendLimitedException();
		}else{
			throw new SmsVerificationCodeSendException("请求发送短信失败: "+ responseEntity);
		} 
	}
}
